package com.example.utils;

import com.example.annotations.*;
import com.example.pojo.Handler;
import org.apache.commons.lang3.StringUtils;
import org.reflections.Reflections;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.regex.Pattern;

/**
 * @author kangshuai
 * servlet初始化工具类
 * <p>
 * 预备：自定义注解：@Service（标记Service层实现类）、@Controller（标记 controller 层）
 * 自定义注解：@RequestMapping（标记 controller 路由）、@Autowired（标记属性）、@Security（标记权限）
 * 0.因为不涉及增强代理，所以设置 一个map类型参数：singletonObject 用来 注册容器；设置一个list 类型用来 封装 url和Method之间的映射关系
 * 1.加载配置文件 springmvc.properties
 * 2.扫描指定目录下所有类
 * 3.对singletonObject进行三次处理
 * （1）处理标记 @service 注解类，判断是否创建实现接口，判断注解是否传值（如果传值以该值定义 id，如果未传值则以类名，并且首字母小写定义传值），等待进一步处理
 * （2）处理标记 @controller 注解类，默认以类名，并且首字母小写定义key
 * （3）对map3中的每个类，判断属性是否标记@Autowired注解，并利用反射内省开启暴力访问，直接向对象属性赋值，无需在类中设置方法赋值，用来赋值的对象是从singletonObject中获取的原有属性
 * 4.处理标记 @RequestMapping 注解 的类，与方法，将 url 路径 ，method ，类型，及方法参数关系顺序，封装进 handler
 * 5.处理标记 @Security 注解 的类 与 方法，将在类与方法上传递的值分别传递到 handler 中对应的参数上
 * 6.将封装好的 handler 放入 list 集合中
 * 5.构造一个HandlerMapping处理器映射器，将配置好的url和Method建立映射关系
 * 7.定义公共方法:singleton(),在外部获取容器集合
 * 8.定义公共方法:build(),在外部获取url和Method之间的映射关系 集合
 */
public class ServletFactory {

    private static Map<String, Object> singletonObject = new HashMap<>();


    private static List<Handler> handlerMapping = new ArrayList<>();


    public static List<Handler> build(String fileName) throws Exception {
        handlerMapping(singleton(fileName));
        return handlerMapping;
    }


    public static Map<String, Object> singleton(String fileName) throws Exception {
        Properties properties = readPropertiesFile(fileName);
        scanPackage(properties.getProperty("scanPackage"));
        autoWiredHandle();
        return singletonObject;
    }

    /**
     * 加载配置文件
     *
     * @param fileName
     * @return
     * @throws Exception
     */
    private static Properties readPropertiesFile(String fileName) throws Exception {
        InputStream resourceAsStream = ServletFactory.class.getClassLoader().getResourceAsStream(fileName);
        Properties properties = new Properties();
        properties.load(resourceAsStream);
        return properties;
    }

    /**
     * 扫描指定包结构下所有文件
     *
     * @param packagePath
     */
    private static void scanPackage(String packagePath) throws Exception {
        Reflections reflections = new Reflections(packagePath);
        serviceClasses(reflections);
        controllerClasses(reflections);
    }

    /**
     * 获取标记@Service注解全部类,
     * 将获取出来的@Service类全部放入map中，判断是否创建实现接口，
     * 判断注解是否传值（如果传值以该值定义 id，
     * 如果未传值则以类明，并且首字母小写定义传值），等待进一步处理
     *
     * @param reflections
     */
    private static void serviceClasses(Reflections reflections) throws Exception {
        String mapKyeName = "";
        for (Class<?> serviceClass : reflections.getTypesAnnotatedWith(Service.class)) {
            //获取该类所实现的所有接口
            Class<?>[] interfaces = serviceClass.getInterfaces();
            for (Class<?> anInterface : interfaces) {
                mapKyeName = StringUtils.isEmpty(serviceClass.getAnnotation(Service.class).value()) ?
                        lowerFirstCase(anInterface.getName().substring(anInterface.getName().lastIndexOf(".") + 1))
                        : serviceClass.getAnnotation(Service.class).value();
                singletonObject.put(mapKyeName, serviceClass.getDeclaredConstructor().newInstance());
            }
        }
    }

    /**
     * 获取标记@Service注解全部类,
     * 将获取出来的@Service类全部放入map中，判断是否创建实现接口，
     * 判断注解是否传值（如果传值以该值定义 id，
     * 如果未传值则以类明，并且首字母小写定义传值），等待进一步处理
     *
     * @param reflections
     */
    private static void controllerClasses(Reflections reflections) throws Exception {
        String mapKyeName = "";
        for (Class<?> controllerClass : reflections.getTypesAnnotatedWith(Controller.class)) {
            mapKyeName = StringUtils.isEmpty(controllerClass.getAnnotation(Controller.class).value()) ?
                    lowerFirstCase(controllerClass.getName().substring(controllerClass.getName().lastIndexOf(".") + 1))
                    : controllerClass.getAnnotation(Controller.class).value();
            singletonObject.put(mapKyeName, controllerClass.getDeclaredConstructor().newInstance());
        }
    }

    /**
     * 对map3中的每个类，判断属性是否标记@Autowired注解，
     * 并利用反射内省向类型对象属性赋值，
     * 用来赋值的对象是从map3中获取的原有属性
     *
     * @throws Exception
     */
    private static void autoWiredHandle() throws Exception {
        String map3KeyName = "";
        for (String map3Key : singletonObject.keySet()) {
            for (Field field : singletonObject.get(map3Key).getClass().getDeclaredFields()) {
                if (field.getAnnotation(Autowired.class) != null) {
                    map3KeyName = StringUtils.isEmpty(field.getAnnotation(Autowired.class).value()) ?
                            lowerFirstCase(field.getType().getName().substring(field.getType().getName().lastIndexOf(".") + 1))
                            : field.getAnnotation(Autowired.class).value();
                    field.setAccessible(true);
                    field.set(singletonObject.get(map3Key), singletonObject.get(map3KeyName));
                    singletonObject.put(map3Key, singletonObject.get(map3Key));
                }
            }
        }
    }

    private static List<Handler> handlerMapping(Map<String, Object> ioc) throws Exception {
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Class<?> aClass = entry.getValue().getClass();
            if (!aClass.isAnnotationPresent(Controller.class)) {
                continue;
            }
            String baseUrl = "";
            if (aClass.isAnnotationPresent(RequestMapping.class)) {
                RequestMapping annotation = aClass.getAnnotation(RequestMapping.class);
                baseUrl = annotation.value().startsWith("/") ? annotation.value() : "/" + annotation.value();
            }
            String[] typeSecurity = null;
            if (aClass.isAnnotationPresent(Security.class)) {
                Security annotation = aClass.getAnnotation(Security.class);
                typeSecurity = annotation.value();
            }
            for (Method method : aClass.getMethods()) {
                if (!method.isAnnotationPresent(RequestMapping.class)) {
                    continue;
                }

                RequestMapping annotation = method.getAnnotation(RequestMapping.class);
                String methodUrl = annotation.value().startsWith("/") ? annotation.value() : "/" + annotation.value();

                String[] methodSecurity = null;
                if (method.isAnnotationPresent(Security.class)) {
                    Security securityAnnotation = method.getAnnotation(Security.class);
                    methodSecurity = securityAnnotation.value();
                }
                Handler handler = new Handler(entry.getValue(), method, Pattern.compile(baseUrl + methodUrl));

                handler.setTypeSecurity(typeSecurity);
                handler.setMethodSecurity(methodSecurity);
                // 计算方法的参数位置信息  // query(HttpServletRequest request, HttpServletResponse response,String name)
                Parameter[] parameters = method.getParameters();
                for (int j = 0; j < parameters.length; j++) {
                    Parameter parameter = parameters[j];

                    if (parameter.getType() == HttpServletRequest.class || parameter.getType() == HttpServletResponse.class) {
                        // 如果是request和response对象，那么参数名称写HttpServletRequest和HttpServletResponse
                        handler.getParamIndexMapping().put(parameter.getType().getSimpleName(), j);
                    } else {
                        handler.getParamIndexMapping().put(parameter.getName(), j);
                    }

                }
                handlerMapping.add(handler);
            }
        }
        return handlerMapping;
    }

    /**
     * 首字母小写
     *
     * @param str
     * @return
     */
    private static String lowerFirstCase(String str) {
        if (Character.isLowerCase(str.charAt(0))) {
            return str;
        }
        char[] chars = str.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }
}